package main

import (
	"bytes"
	"context"
	"fmt"
	"log"
	"os/exec"
	"runtime"
	"sync"
	"time"
)

var (
	_Tasks = NewTasks()
	lk     sync.RWMutex
)

type task struct {
	ID       string   `json:"id"`
	Cmd      string   `json:"cmd"`
	Args     []string `json:"args"`
	Interval int      `json:"interval"` // unit second

	next time.Time
}

func (t *task) touch(now time.Time) {
	t.next = now.Add(time.Duration(t.Interval) * time.Second)
}

func (t *task) run() {
	defer func() {
		if r := recover(); r != nil {
			const size = 64 << 10
			buf := make([]byte, size)
			buf = buf[:runtime.Stack(buf, false)]
			log.Printf("cron: panic running job: %v\n%s\n", r, buf)
		}
	}()

	var (
		cmd *exec.Cmd
		b   bytes.Buffer
	)
	if *timeout > 0 {
		ctx, cancel := context.WithTimeout(context.Background(), time.Duration(*timeout)*time.Second)
		defer cancel()
		cmd = exec.CommandContext(ctx, t.Cmd, t.Args...)
	} else {
		cmd = exec.Command(t.Cmd, t.Args...)
	}

	cmd.Stdout = &b
	cmd.Stderr = &b
	if err := cmd.Start(); err != nil {
		log.Printf("task[%s] run err: %s", t.ID, err.Error())
		return
	}

	if err := cmd.Wait(); err != nil {
		log.Printf("task[%s] run err: %s", t.ID, err.Error())
		return
	}

	log.Printf("task[%s] output: %s", t.ID, b.String())
}

type tasks map[string]*task

func NewTasks() tasks {
	return make(tasks)
}

// Add add a task
func (ts tasks) Add(t *task) error {
	lk.Lock()
	_, ok := _Tasks[t.ID]
	if ok {
		lk.Unlock()
		return fmt.Errorf("The task [%s] already exists.", t.ID)
	}

	_Tasks[t.ID] = t
	lk.Unlock()
	_Cron.addChan <- t
	return nil
}

// Get get a task desc
func (ts tasks) Get(id string) *task {
	lk.RLock()
	t := _Tasks[id]
	lk.RUnlock()
	return t
}

// Del delete a task
func (ts tasks) Del(id string) error {
	lk.Lock()
	_, ok := _Tasks[id]
	if !ok {
		lk.Unlock()
		return fmt.Errorf("The task [%s] is not found.", id)
	}
	delete(_Tasks, id)
	lk.Unlock()
	_Cron.delChan <- id
	return nil
}

// List list tasks' id
func (ts tasks) List() []string {
	if len(_Tasks) == 0 {
		return nil
	}

	ids := make([]string, 0, len(_Tasks))
	for id := range _Tasks {
		ids = append(ids, id)
	}
	return ids
}
